﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using repliback4.Xml;
using Tools.Debugging;
using repliback4.Net.Protocol.Messages;

namespace repliback4.Net.Protocol {
    public class StreamContext {
        public XmlParserContext parserContext;
        public Messages.BaseStream stream;
        public StreamContext ( ) {

        }

    }

    public class MessageParser {
        public delegate void OnStreamErrorHandler ( );
        public event OnStreamErrorHandler OnStreamError;

        public delegate void OnStreamBeginHandler ( BaseStream stream );
        public event OnStreamBeginHandler OnStreamBegin;

        public delegate void OnStreamEndHandler ( BaseStream stream );
        public event OnStreamEndHandler OnStreamEnd;

        public delegate void OnXmlDeclarationHandler ( XmlNode xmlDeclaration );
        public event OnXmlDeclarationHandler OnXmlDeclaration;

        public delegate void OnTagHandler ( ReplibackTag stanza );
        public event OnTagHandler OnTag;

        protected XmlReader xrdr;
        protected TextReader rdr;
        protected Stack<StreamContext> streamScope = new Stack<StreamContext>( );
        protected TagRegistry registry = new TagRegistry( );

        #region :: Xml Reader Section ::

        protected XmlDocument xdoc = new XmlDocument( );
        protected XmlReaderSettings xrdrs = new XmlReaderSettings( ) {
            ConformanceLevel = ConformanceLevel.Fragment,
            IgnoreComments = true,
            IgnoreWhitespace = true,
            
        };
        protected XmlNameTable nt = new NameTable( );
        protected XmlNamespaceManager nsMan;

        #endregion

        public MessageParser ( ) {
            this.nsMan = new XmlNamespaceManager( this.nt );

            registry.Register( typeof( repliback4.Net.Protocol.Messages.Stream ) );
            registry.Register( typeof( repliback4.Net.Protocol.Messages.FeaturesStream ) );
            
            registry.Register( typeof( repliback4.Net.Protocol.Messages.InfoQuery ) );
            
            registry.Register( typeof( repliback4.Net.Protocol.Messages.SASLMechanisms ) );
            registry.Register( typeof( repliback4.Net.Protocol.Messages.SASLAuth ) );
            registry.Register( typeof( repliback4.Net.Protocol.Messages.SASLAuthFailure ) );
            registry.Register( typeof( repliback4.Net.Protocol.Messages.SASLAuthSuccess ) );
            
            registry.Register( typeof( repliback4.Net.Protocol.Messages.StartTLS ) );
            registry.Register( typeof( repliback4.Net.Protocol.Messages.ProceedTLS ) );


            registry.Register( typeof( repliback4.Net.Protocol.Messages.Bind ) );
            registry.Register( typeof( repliback4.Net.Protocol.Messages.Session ) );

            
        }
        public void Parse ( String message ) {

            // parser stop tag
            message += "<stop xmlns=\"parser\"/>";

            this.rdr = new StringReader( message );
            if ( this.streamScope.Count > 0 ) {
                StreamContext sContext = this.streamScope.Peek( );
                this.xrdr = XmlReader.Create( this.rdr, this.xrdrs, sContext.parserContext );
            } else {
                XmlParserContext xContext = new XmlParserContext( null, this.nsMan, null, XmlSpace.None );
                this.xrdr = XmlReader.Create( this.rdr, this.xrdrs, xContext );
            }
            //this.xrdr.XmlSpace = XmlSpace.
            this.xrdr.Read( );
            try {
                XmlNode node = null;
                Boolean stop = false;
                ReplibackTag tag;

                do {
                    if ( this.IsStream( new XmlQualifiedName( this.xrdr.LocalName, this.xrdr.NamespaceURI ) ) ) {

                        switch ( this.xrdr.NodeType ) {
                            case XmlNodeType.Element:
                                try {
                                    node = this.ReadStreamElement( );
                                    stop = node == null;
                                    tag = this.registry.GetTag( node );

                                    StreamContext sContext = new StreamContext( );

                                    sContext.parserContext = new XmlParserContext( null, this.nsMan, this.xrdr.XmlLang, XmlSpace.Preserve );
                                    sContext.stream = /*(Messages.BaseStream)*/tag as Messages.BaseStream;
                                    this.streamScope.Push( sContext );


                                    try {
                                        if ( this.OnStreamBegin != null ) this.OnStreamBegin( tag as BaseStream );
                                    } catch ( Exception e ) {
                                        Logger.Debug( this, "OnStreamBegin handler exception: [{0}]", e.Message );
                                    }

                                } catch ( Exception e ) {
                                    Logger.Debug( this, e.Message );
                                }
                                break;
                            case XmlNodeType.EndElement:

                                try {
                                    if ( this.OnStreamEnd != null ) this.OnStreamEnd( null );
                                    this.xrdr.ReadEndElement( );
                                    this.xrdr.MoveToContent( );
                                } catch ( Exception  ) {
                                    //Logger.DebugFormat( this, "OnStreamEnd handler exception: [{0}]", e.Message );
                                    stop = true;
                                }
                                this.streamScope.Pop( );
                                break;
                        }
                    } else {
                        try {
                            node = this.ReadNode( xdoc, xrdr );
                            stop = node == null;
                        } catch ( XmlException e ) {
                            Logger.Debug( this, e.Message );
                        } catch ( Exception e ) {
                            Logger.Debug( this, e.Message );
                        }
                        if ( node != null ) {
                            switch ( node.NodeType ) {
                                case XmlNodeType.Element:
                                    try {
                                        tag = this.registry.GetTag( ( XmlElement ) node );
                                        try {
                                            if ( this.OnTag != null ) this.OnTag( tag );
                                        } catch ( Exception e ) {
                                            Logger.Debug( this, "OnTag handler exception: [{0}]", e.Message );
                                        }
                                    } catch ( TagRegistryException e ) {
                                        switch ( e.ErrorCode ) {
                                            case TagRegistryErrorCodes.UnknownTag:
                                                Logger.Debug( this, "TagRegistryException::UnknownTag: [{0}] ignoring", e.Message );
                                                break;
                                            default:
                                                Logger.Debug( this, "other TagRegistryException: [{0}]", e.Message );
                                                break;
                                        }
                                    } catch ( Exception  ) {
                                        Logger.Debug( this, "unknown GetTag exception" );
                                    }
                                    break;
                                case XmlNodeType.XmlDeclaration:
                                    try {
                                        if ( this.OnXmlDeclaration != null ) this.OnXmlDeclaration( node );
                                    } catch ( Exception e ) {
                                        Logger.Debug( this, "OnXmlDeclaration handler exception: [{0}]", e.Message );
                                    } break;
                            }
                        }
                        //else
                        //{
                        //    stop = true;
                        //}

                    }
                } while ( !stop );
                this.nsMan.PopScope( );
            } catch ( Exception e ) {
                Logger.Debug( this, "stream error exception [{0}]\r\n{1}", e.Message, e.StackTrace );
                if ( this.OnStreamError != null ) this.OnStreamError( );

            }

        }

        private XmlNode ReadNode ( XmlDocument xdoc, XmlReader xrdr ) {
            //check if it is <stop/> element
            if ( new XmlQualifiedName( xrdr.Name, xrdr.NamespaceURI ).ToString( ) == "parser:stop" ) {
                return null;
            }

            try {
                return xdoc.ReadNode( xrdr );
            } catch ( Exception  ) {
                //failed to read with built-in procedures mostly because parent tag is not closed,
            }

            throw new Exception( "stream error" );

        }

        protected XmlElement ReadStreamElement ( ) {
            XmlElement element = this.xdoc.CreateElement( this.xrdr.Prefix, this.xrdr.LocalName, this.xrdr.NamespaceURI );
            if ( this.xrdr.HasAttributes ) {
                if ( this.xrdr.MoveToFirstAttribute( ) ) {
                    XmlNode attr = null;
                    do {
                        attr = xdoc.ReadNode( this.xrdr );
                        element.SetAttributeNode( ( XmlAttribute ) attr );
                        if ( !this.xrdr.MoveToNextAttribute( ) ) {

                            attr = null;
                        }
                    } while ( attr != null );
                }
            }
            try {
                Boolean b = this.xrdr.MoveToElement( );
                this.xrdr.ReadStartElement( );
                //this.xrdr.ReadStartElement( );
            } catch ( Exception  ) {
                String s = this.rdr.ReadToEnd( );
            }

            return element;

        }
        protected static HashSet<String> streamNamespaces = new HashSet<String>( new String[] { 
            JabberNamespaces.JABBER_STREAM, 
            ReplibackNamespaces.REPLIBACK_STREAM 
        } );
        protected Boolean IsStream ( XmlQualifiedName xqName ) {
            return streamNamespaces.Contains( xqName.Namespace );
        }

    }
}
